package it.uniroma2.art.coda.core;

import it.uniroma2.art.coda.interfaces.ParserPR;
import it.uniroma2.art.coda.pearl.model.PlaceholderStruct;
import it.uniroma2.art.coda.structures.ValueOrUri;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.jcas.cas.BooleanArray;
import org.apache.uima.jcas.cas.DoubleArray;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FSList;
import org.apache.uima.jcas.cas.FloatArray;
import org.apache.uima.jcas.cas.IntegerArray;
import org.apache.uima.jcas.cas.LongArray;
import org.apache.uima.jcas.cas.NonEmptyFSList;
import org.apache.uima.jcas.cas.NonEmptyStringList;
import org.apache.uima.jcas.cas.StringArray;
import org.apache.uima.jcas.cas.StringList;
import org.apache.uima.jcas.tcas.Annotation;

/**
 * This class contains some methid that can be used as utilities regarding the use of UIMA in CODA
 * 
 * @author Andrea Turbati
 * 
 */
public class UIMACODAUtilities {

	private static final String STRINGARRAY = "uima.cas.StringArray";
	private static final String INTEGERARRAY = "uima.cas.IntegerArray";
	private static final String LONGARRAY = "uima.cas.LongArray";
	private static final String BOOLEANARRAY = "uima.cas.BooleanArray";
	private static final String DOUBLEARRAY = "uima.cas.DoubleArray";
	private static final String FLOATARRAY = "uima.cas.FloatArray";
	private static final String FSARRAY = "uima.cas.FSArray";
	private static final String NONEMPTYSTRINGLIST = "uima.cas.NonEmptyStringList";
	private static final String STRINGLIST = "uima.cas.StringList";
	private static final String NONEMPTYFSLIST = "uima.cas.NonEmptyFSList";
	private static final String FSLIST = "uima.cas.FSList";

//	/**
//	 * Get a value (normally the first if more than one is present) in a particular annotation following the
//	 * feature path contained in the placeholder
//	 * 
//	 * @param ann
//	 *            the annotation where the value will be taken from
//	 * @param placeholder
//	 *            the placeholder containing the feature path
//	 * @return the one (just one) corresponding to the feature path or null if there is no value
//	 */
//	public static String getValueOfFeatureFromFeatPathOneValue(Annotation ann, PlaceholderStruct placeholder) {
//		String uimaTypeAndFeat = placeholder.getType()+placeholder.getFeaturePath();
//		return getValueOfFeatureFromFeatPathOneValue(ann, uimaTypeAndFeat);
//	}

	/**
	 * Get a value (normally the first if more than one is present) in a particular annotation following the
	 * feature path represented by uimaTypeAndFeat
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param uimaTypeAndFeat
	 *            the feature path
	 * @return the one (just one) corresponding to the feature path or null if there is no value
	 */
	public static String getValueOfFeatureFromFeatPathOneValue(Annotation ann, String uimaTypeAndFeat) {
		List<String> valueList = getValuesOfFeatureFromFeatPath(ann, uimaTypeAndFeat);
		if (valueList == null)
			return null;
		else
			return valueList.get(0);
	}

	/**
	 * Get a list of values in a particular annotation following the feature path (or uri) contained in the placeholder.
	 * It is able to resolve also the if/if then/else construct when present
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param placeholder
	 *            the placeholder containing the feature path
	 * @return the list of values corresponding to the feature path or null if there is no value
	 */
	/*public static List<ValueOrUri> getValuesOrUriOfPlaceholder(Annotation annotation, PlaceholderStruct placeholder, 
			ParserPR projectionRuleParserSelected, Map<PlaceholderStruct, String> placeholderAliasToValueMap) {
		String uimaTypeAndFeat="";
		String uri="";
		List<ValueOrUri> valueOrUriList = new ArrayList<ValueOrUri>();
		if(placeholder.hasIfElse()){
			// the placeholder has an if/else structure, so I need to test each condition to know
			// which uima typeFeat (or uri) should I use
			if(projectionRuleParserSelected == null || placeholderAliasToValueMap == null)
				return null;
			List<IfElseStruct> ifElseList = placeholder.getIfElseList();
			for (int i = 0; i < ifElseList.size(); ++i) {
				IfElseStruct ifElse = ifElseList.get(i);
				if (ifElse.isElse() == true) {
					// the condition is an else, so there is no boolean condition to check (and by
					// construction it is the last in the list)
					if (ifElse.isUimaTypeAndFeatSet())
						uimaTypeAndFeat = ifElse.getUimaTypeAndFeat();
					else
						uri = ifElse.getOntoResUri();
					break;
				}
				if (projectionRuleParserSelected.checkCondition(ifElse, placeholderAliasToValueMap,
						annotation) == true) {
					// the boolean condition is true, so I can use the uimaTypeAndFeat of this
					// IfElseStruct
					if (ifElse.isUimaTypeAndFeatSet())
						uimaTypeAndFeat = ifElse.getUimaTypeAndFeat();
					else
						uri = ifElse.getOntoResUri();
					break;
				}
			}
			if (uimaTypeAndFeat.compareTo("") == 0 && uri.compareTo("") == 0)
				return valueOrUriList; // no uimaType or URI could be used because each boolean condition in the
										// if/else was false
			else if(uri.compareTo("") != 0){ // a uri was specified and not a feature path
				ValueOrUri valueOrUri = new ValueOrUri(uri, true);
				valueOrUriList.add(valueOrUri);
				return valueOrUriList;
			}
		}
		else
			uimaTypeAndFeat = placeholder.getTypeAndFeat()+":"+p;
		
		List <String> valueList = getValuesOfFeatureFromFeatPath(annotation, uimaTypeAndFeat);
		for(String value : valueList)
			valueOrUriList.add(new ValueOrUri(value, false));
		return valueOrUriList;
	}*/

	/**
	 * Get a list of values in a particular annotation following the feature path represented by
	 * uimaTypeAndFeat
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param uimaTypeAndFeat
	 *            the feature path
	 * @return the list of values corresponding to the feature path or null if there is no value
	 */
	public static List<String> getValuesOfFeatureFromFeatPath(Annotation ann, String uimaTypeAndFeat) {

		List<String> featurePathValueList = new ArrayList<String>();

		if (uimaTypeAndFeat == "")
			return null;

		// split the uimaTypeAndFeat first with : (Type And Features), then the Features with / (path)
		String[] typeAndFeatArray = uimaTypeAndFeat.split(":");

		// check if there is a feature path or if there is just the uima type
		if (typeAndFeatArray.length == 1) {
			// there is no featurepath, but the uimaTypeAndFeat can have : at the end
			String uimaType = uimaTypeAndFeat.replace(":", "");
			if (ann.getType().getName().compareTo(uimaType) == 0) {
				featurePathValueList.add(ann.getCoveredText());
				return featurePathValueList;
			} else
				return null;
		}
		// there is a feature path, so split the single component of the path (feature) in an array
		String[] featurePathElementNamesArray = typeAndFeatArray[1].split("/");

		return getValueOfFeature(ann, featurePathElementNamesArray, 0);
	}

	private static List<String> getValueOfFeature(FeatureStructure featStruct, String[] path, int posFeatPath) {
		if (posFeatPath > path.length)
			return null;
		if (featStruct == null)
			return null;
		String featName = path[posFeatPath];
		Feature feature = featStruct.getType().getFeatureByBaseName(featName.split("\\[")[0]);

		if (feature == null)
			return null;

		// check if we are analyzing the last element of the array and it is a primitive.
		// In this case return it, otherwise return null
		if (path.length == posFeatPath + 1) {
			if (feature.getRange().isPrimitive()) {
				List<String> valueList = new ArrayList<String>();
				String value = featStruct.getFeatureValueAsString(feature);
				valueList.add(value);
				return valueList;
			} else
				return null;
		}

		// check if the uimaType is an Array
		if (feature.getRange().isArray()) {
			boolean posSpecified = false;
			int posList = -1;
			if (featName.contains("[")) {
				posList = Integer.parseInt(featName.split("\\[")[1].split("\\]")[0]);
				posSpecified = true;
			}
			return getValueOfFeatureFromArray(featStruct.getFeatureValue(feature), posFeatPath, path,
					posSpecified, posList);
		}
		// check if the uimaType is a List
		else if (feature.getRange().getName().startsWith("uima.cas")
				&& feature.getRange().getName().endsWith("List")) {
			boolean posSpecified = false;
			int posList = -1;
			if (featName.contains("[")) {
				posList = Integer.parseInt(featName.split("\\[")[1].split("\\]")[0]);
				posSpecified = true;
			}
			return getValueOfFeatureFromList(featStruct.getFeatureValue(feature), posFeatPath, path,
					posSpecified, posList);
		}
		// the featureStruct is not a List nor an Array, so it must be a complex one
		List<String> stringValueArray = new ArrayList<String>();
		List<String> valueList = getValueOfFeature(featStruct.getFeatureValue(feature), path, posFeatPath + 1);
		if (valueList == null)
			return null;
		stringValueArray.addAll(valueList);
		return stringValueArray;

	}

	private static List<String> getValueOfFeatureFromList(FeatureStructure featStruct, int posFeatPath,
			String[] path, boolean posSpecified, int posList) {
		if (featStruct == null)
			return null;
		Type uimaType = featStruct.getType();
		if ((uimaType.getName().compareTo(NONEMPTYFSLIST) == 0)) {
			FSList fsList = (FSList) featStruct;
			if (posSpecified) {
				FeatureStructure nextFeatStruct = getElemFromListAtPos(fsList, posList);
				if (nextFeatStruct == null)
					return null;
				return getValueOfFeature(nextFeatStruct, path, posFeatPath + 1);
			} else {
				List<String> valueList = new ArrayList<String>();

				List<FeatureStructure> featStructList = getAllElemFromList(fsList);
				for (FeatureStructure featStructFromList : featStructList) {
					List<String> valueListTemp = getValueOfFeature(featStructFromList, path, posFeatPath + 1);
					valueList.addAll(valueListTemp);
				}
				return valueList;
			}
		} else if ((uimaType.getName().compareTo(NONEMPTYSTRINGLIST) == 0)) {
			StringList stringList = (StringList) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				String value = getElemFromListAtPos(stringList, posList);
				if (value == null)
					return null;
				List<String> valueList = new ArrayList<String>();
				valueList.add(value);
				return valueList;
			} else {
				return getAllElemFromList(stringList);
			}
		}
		return null;

	}

	private static FeatureStructure getElemFromListAtPos(FSList fsList, int pos) {
		FSList tempList = fsList;
		for (int i = 0; i < pos; ++i) {
			if (tempList instanceof NonEmptyFSList)
				tempList = ((NonEmptyFSList) tempList).getTail();
			else
				return null;
		}
		if (tempList instanceof NonEmptyFSList)
			return ((NonEmptyFSList) tempList).getHead();
		else
			return null;
	}

	private static List<FeatureStructure> getAllElemFromList(FSList fsList) {
		FSList tempFSList = fsList;
		List<FeatureStructure> featStructList = new ArrayList<FeatureStructure>();
		while (true) {
			if (tempFSList instanceof NonEmptyFSList) {
				featStructList.add(((NonEmptyFSList) tempFSList).getHead());
				tempFSList = ((NonEmptyFSList) tempFSList).getTail();
			} else
				break;
		}
		return featStructList;
	}

	private static String getElemFromListAtPos(StringList stringList, int pos) {
		StringList tempList = stringList;
		for (int i = 0; i < pos; ++i) {
			if (tempList instanceof NonEmptyStringList)
				tempList = ((NonEmptyStringList) tempList).getTail();
			else
				return null;
		}
		if (tempList instanceof NonEmptyStringList)
			return ((NonEmptyStringList) tempList).getHead();
		else
			return null;
	}

	private static List<String> getAllElemFromList(StringList stringList) {
		StringList tempStringList = stringList;
		List<String> listOfString = new ArrayList<String>();
		while (true) {
			if (tempStringList instanceof NonEmptyStringList) {
				listOfString.add(((NonEmptyStringList) tempStringList).getHead());
				tempStringList = ((NonEmptyStringList) tempStringList).getTail();
			} else
				break;
		}
		return listOfString;
	}

	private static List<String> getValueOfFeatureFromArray(FeatureStructure featStruct, int posFeatPath,
			String[] path, boolean posSpecified, int posArray) {
		Type uimaType = featStruct.getType();
		List<String> valueList = new ArrayList<String>();
		if (uimaType.getName().compareTo(INTEGERARRAY) == 0) {
			IntegerArray intArray = (IntegerArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (intArray.size() > posArray) {
					valueList.add("" + intArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < intArray.size(); ++i)
					valueList.add("" + intArray.get(i));
			}
		} else if (uimaType.getName().compareTo(BOOLEANARRAY) == 0) {
			BooleanArray booleanArray = (BooleanArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (booleanArray.size() > posArray) {
					valueList.add("" + booleanArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < booleanArray.size(); ++i)
					valueList.add("" + booleanArray.get(i));
			}
		} else if (uimaType.getName().compareTo(LONGARRAY) == 0) {
			LongArray longArray = (LongArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (longArray.size() > posArray) {
					valueList.add("" + longArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < longArray.size(); ++i)
					valueList.add("" + longArray.get(i));
			}
		} else if (uimaType.getName().compareTo(DOUBLEARRAY) == 0) {
			DoubleArray doubleArray = (DoubleArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (doubleArray.size() > posArray) {
					valueList.add("" + doubleArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < doubleArray.size(); ++i)
					valueList.add("" + doubleArray.get(i));
			}
		} else if (uimaType.getName().compareTo(FLOATARRAY) == 0) {
			FloatArray floatArray = (FloatArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (floatArray.size() > posArray) {
					valueList.add("" + floatArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < floatArray.size(); ++i)
					valueList.add("" + floatArray.get(i));
			}
		} else if (uimaType.getName().compareTo(STRINGARRAY) == 0) {
			StringArray stringArray = (StringArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (stringArray.size() > posArray) {
					valueList.add("" + stringArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < stringArray.size(); ++i)
					valueList.add("" + stringArray.get(i));
			}
		} else { // the array has not primitive value
			FSArray fsArray = (FSArray) featStruct;
			if (posSpecified) {
				if (fsArray.size() > posArray) {
					return getValueOfFeature(fsArray.get(posArray), path, posFeatPath + 1);
				} else
					return null;
			} else {
				for (int i = 0; i < fsArray.size(); ++i) {
					List<String> valueListTemp = getValueOfFeature(fsArray.get(i), path, posFeatPath + 1);
					if (valueListTemp != null)
						valueList.addAll(valueListTemp);
				}
			}
		}
		return valueList;
	}

	private static List<String> getValueOfFeatureGeneric(FeatureStructure featStruct, int posFeatPath, String[] path) {
		Type uimaType = featStruct.getType();

		return null;

	}
}
